package com.cens.mall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.cens.common.to.es.SkuESModel;
import com.cens.common.utils.R;
import com.cens.mall.search.config.ESConfig;
import com.cens.mall.search.constant.EsConstant;
import com.cens.mall.search.feign.CommodityFeignService;
import com.cens.mall.search.service.MallSearchService;
import com.cens.mall.search.vo.AttrResponseVo;
import com.cens.mall.search.vo.SearchParam;
import com.cens.mall.search.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.apache.lucene.util.QueryBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private CommodityFeignService commodityFeignService;

    @Override
    public SearchResult search(SearchParam searchParam) {
        //动态构建DSL查询语句
        SearchResult result = null;
        //构建检索条件
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            //检索
            SearchResponse response = client.search(searchRequest, ESConfig.COMMON_OPTIONS);
            //解析响应数据
            result = buildSearchResult(searchParam, response);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private SearchResult buildSearchResult(SearchParam param, SearchResponse response) {
        SearchHits hits = response.getHits();
        SearchResult searchResult = new SearchResult();
        //1、商品信息
        List<SkuESModel> products = new ArrayList<>();
        if (hits.getHits() != null && hits.getHits().length > 0) {
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                SkuESModel skuESModel = JSON.parseObject(sourceAsString, SkuESModel.class);
                if (!StringUtils.isEmpty(param.getKeyword())) {
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String s = skuTitle.getFragments()[0].toString();
                    skuESModel.setSkuTitle(s);
                }
                products.add(skuESModel);
            }
        }
        searchResult.setProducts(products);
        //2、商品的所有属性信息
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();
        ParsedNested attr_agg = response.getAggregations().get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //获取属性id
            attrVo.setAttrId(Long.parseLong(bucket.getKeyAsString()));
            //获取属性名称
            ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
            attrVo.setAttrName(attr_name_agg.getBuckets().get(0).getKeyAsString());
            //获取属性值
            ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = attr_value_agg.getBuckets().stream().map(b -> b.getKeyAsString()).collect(Collectors.toList());
            attrVo.setAttrValue(attrValues);
            //add
            attrVos.add(attrVo);
        }
        searchResult.setAttrs(attrVos);
        //3、商品的所有品牌信息
        List<SearchResult.BrandVo> brandVos = new ArrayList<>();
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        for (Terms.Bucket bucket : brand_agg.getBuckets()) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            //获取品牌id
            brandVo.setBrandId(Long.parseLong(bucket.getKeyAsString()));
            //获取品牌名称
            ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
            brandVo.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());
            //获取品牌图片
            ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
            brandVo.setBrandImg(brand_img_agg.getBuckets().get(0).getKeyAsString());
            //add
            brandVos.add(brandVo);
        }
        searchResult.setBrands(brandVos);
        //4、商品的所有分类信息
        List<SearchResult.CategoryVo> catalogVos = new ArrayList<>();
        ParsedLongTerms catalog_agg = response.getAggregations().get("catalog_agg");
        for (Terms.Bucket bucket : catalog_agg.getBuckets()) {
            SearchResult.CategoryVo categoryVo = new SearchResult.CategoryVo();
            //获取分类id
            categoryVo.setCatalogId(Long.parseLong(bucket.getKeyAsString()));
            //获取分类名
            ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            categoryVo.setCatalogName(catalog_name_agg.getBuckets().get(0).getKeyAsString());
            //add
            catalogVos.add(categoryVo);
        }
        searchResult.setCategories(catalogVos);
        //5、分页信息
        searchResult.setPageNum(param.getPageNum());
        searchResult.setTotal(hits.getTotalHits().value);
        //总页码
        long l = searchResult.getTotal() / EsConstant.PAGE_SIZE;
        searchResult.setTotalPages((int) (searchResult.getTotal() % EsConstant.PAGE_SIZE == 0 ? l : l + 1));
        //页码导航
        List<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= l; i++) {
            pageNavs.add(i);
        }
        searchResult.setPageNavs(pageNavs);
        /**
         * 面包屑导航
         */
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            List<SearchResult.NavVo> navVos = param.getAttrs().stream()
                    .map(
                            attr -> {
                                SearchResult.NavVo navVo = new SearchResult.NavVo();
                                String[] split = attr.split("-");
                                navVo.setNavValue(split[1]);
                                R r = commodityFeignService.attrInfo(Long.parseLong(split[0]));
                                if (r.getCode() == 0) {
                                    AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {
                                    });
                                    navVo.setNavName(data.getAttrName());
                                    searchResult.getAttrIds().add(data.getAttrId());
                                } else {
                                    navVo.setNavName(split[0]);
                                }
                                String replace = replaceQueryString(param, "attrs", attr);
                                navVo.setLink("http://search.censmall.com/list.html?" + replace);

                                return navVo;
                            }
                    ).collect(Collectors.toList());
            searchResult.setNavs(navVos);
        }
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            List<SearchResult.NavVo> navs = searchResult.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();
            navVo.setNavName("品牌");
            String replace = "";
            StringBuffer sb = new StringBuffer();
            for (SearchResult.BrandVo brand : searchResult.getBrands()) {
                sb.append(brand.getBrandName());
                replace = replaceQueryString(param, "brandId", brand.getBrandId().toString());
            }
            navVo.setNavValue(sb.toString());
            navVo.setLink("http://search.censmall.com/list.html?" + replace);
            navs.add(navVo);
            searchResult.setNavs(navs);
        }
        return searchResult;
    }

    private String replaceQueryString(SearchParam param, String key, String value) {
        String encode = null;
        try {
            encode = URLEncoder.encode(value, "UTF-8");
            encode.replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return param.get_queryString().replace("&" + key + "=" + encode, "");
    }

    /**
     * 构建检索条件
     * 模糊匹  配。过滤。排序，分页，高亮，聚合分析
     *
     * @return SearchRequest
     */
    private SearchRequest buildSearchRequest(SearchParam param) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        /*
         * 查询条件
         */
        //构建 bool query
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //must 模糊匹配
        if (!StringUtils.isEmpty(param.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
        }
        //filter 过滤 ，按照3级分类id进行查询
        if (param.getCatalog3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }
        //filter 过滤，按照品牌id过滤
        if (CollectionUtils.isNotEmpty(param.getBrandId())) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId().toArray()));
        }
        //filter 按照指定的属性进行查询
        if (CollectionUtils.isNotEmpty(param.getAttrs())) {
            for (String attrStr : param.getAttrs()) {
                BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                String[] s = attrStr.split("-");
                //属性id
                String attrId = s[0];
                //属性值
                String[] attrValues = s[1].split(":");
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                //构建嵌入式查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                //构建
                boolQueryBuilder.filter(nestedQuery);
            }
        }
        //filter 按照是否拥有库存进行查询
        if (param.getHasStock() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() > 0));
        }
        //filter 按照价格区间进行检索
        if (!StringUtils.isEmpty(param.getSkuPrice())) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] values = param.getSkuPrice().split("_");
            if (values.length == 2) {
                rangeQuery.gte(values[0]).lte(values[1]);
            } else if (values.length == 1) {
                if (param.getSkuPrice().startsWith("_")) {
                    rangeQuery.lte(values[0]);
                }
                if (param.getSkuPrice().endsWith("_")) {
                    rangeQuery.gte(values[0]);
                }
            }
            boolQueryBuilder.filter(rangeQuery);
        }
        //添加查询条件
        searchSourceBuilder.query(boolQueryBuilder);

        /*
         * 排序
         */
        if (!StringUtils.isEmpty(param.getSort())) {
            String sort = param.getSort();
            String[] s = sort.split("_");
            searchSourceBuilder.sort(s[0], s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC);
        }
        /*
         * 分页
         */
        searchSourceBuilder.from((param.getPageNum() - 1) * EsConstant.PAGE_SIZE);
        searchSourceBuilder.size(EsConstant.PAGE_SIZE);
        /*
         * 高亮显示
         */
        if (!StringUtils.isEmpty(param.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }
        /*
         *聚合
         */
        //1、品牌聚合
        TermsAggregationBuilder brandAggregation = AggregationBuilders.terms("brand_agg")
                .field("brandId")
                .size(10)
                .subAggregation(
                        AggregationBuilders.terms("brand_name_agg")
                                .field("brandName")
                                .size(10)
                )
                .subAggregation(
                        AggregationBuilders.terms("brand_img_agg")
                                .field("brandImg")
                                .size(10)
                );
        searchSourceBuilder.aggregation(brandAggregation);
        //2、分类聚合
        TermsAggregationBuilder catalogAggregation = AggregationBuilders.terms("catalog_agg")
                .field("catalogId")
                .size(10)
                .subAggregation(
                        AggregationBuilders.terms("catalog_name_agg")
                                .field("catalogName")
                                .size(1)
                );
        searchSourceBuilder.aggregation(catalogAggregation);
        //3、属性聚合
        NestedAggregationBuilder attrAggregation = AggregationBuilders.nested("attr_agg", "attrs")
                .subAggregation(
                        AggregationBuilders.terms("attr_id_agg")
                                .field("attrs.attrId")
                                .size(10)
                                .subAggregation(
                                        AggregationBuilders.terms("attr_name_agg")
                                                .field("attrs.attrName")
                                                .size(1)
                                )
                                .subAggregation(
                                        AggregationBuilders.terms("attr_value_agg")
                                                .field("attrs.attrValue")
                                                .size(10)
                                )
                );
        searchSourceBuilder.aggregation(attrAggregation);

        String dsl = searchSourceBuilder.toString();
        System.out.println(dsl);

        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.COMMODITY_INDEX}, searchSourceBuilder);
        return searchRequest;
    }
}
